Test Failed
Push — master ( 2f48cb...59db98 )
by Emil
03:56
created

auth.js ➔ isValidAPIKey   A

Complexity

Conditions 5

Size

Total Lines 27
Code Lines 17

Duplication

Lines 25
Ratio 92.59 %

Code Coverage

Tests 8
CRAP Score 5.0342

Importance

Changes 0
Metric Value
eloc 17
dl 25
loc 27
ccs 8
cts 9
cp 0.8889
rs 9.0833
c 0
b 0
f 0
cc 5
crap 5.0342
1 1
const db = require("../db/database.js");
2 1
const hat = require("hat");
3 1
const validator = require("email-validator");
4
5 1
const bcrypt = require('bcryptjs');
6 1
const jwt = require('jsonwebtoken');
7
8
9
const jwtSecret = process.env.JWT_SECRET;
10 1
11 1
module.exports = (function () {
12
    function isValidAPIKey(apiKey, next, path, res) {
13 1 View Code Duplication
        db.get("SELECT email FROM apikeys WHERE key = ?", apiKey, (err, row) => {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
14
            if (err) {
15
                return res.status(500).json({
16 2
                    errors: {
17
                        status: 500,
18 1
                        source: path,
19
                        title: "Database error",
20 100
                        detail: err.message
21 100
                    }
22
                });
23
            }
24
25
            if (row !== undefined) {
26
                return next();
27
            }
28
29
            res.status(401).json({
30
                errors: {
31
                    status: 401,
32 100
                    source: path,
33 91
                    title: "Valid API key",
34
                    detail: "No valid API key provided."
35
                }
36 9
            });
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
37
        });
38
    }
39
40
    function getNewAPIKey(res, path, email) {
41
        if (email === undefined || !validator.validate(email)) {
42
            res.status(401).json({
43
                errors: {
44
                    status: 401,
45
                    source: path,
46
                    title: "Valid email",
47
                    detail: "A valid email address is required to obtain an API key."
48 11
                }
49 2
            });
50
        } else {
51
            db.get("SELECT email, key FROM apikeys WHERE email = ?", email, (err, row) => {
52
                if (err) {
53
                    return res.status(500).json({
54
                        errors: {
55
                            status: 500,
56
                            source: path,
57
                            title: "Database error",
58 9
                            detail: err.message
59 9
                        }
60
                    });
61
                }
62
63
                if (row !== undefined) {
64
                    return res.json({
65
                        data: {
66
                            message: "Email address already used for api key.",
67
                            apiKey: row.key
68
                        }
69
                    });
70 9
                }
71 1
72
                getUniqueAPIKey(res, path, email);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
73
            });
74
        }
75
    }
76
77
    function getUniqueAPIKey(res, path, email) {
78
        const apiKey = hat();
79 8
80
        db.get("SELECT key FROM apikeys WHERE key = ?", apiKey, (err, row) => {
81
            if (err) {
82
                return res.status(401).json({
83
                    errors: {
84
                        status: 401,
85 8
                        source: path,
86
                        title: "Database error",
87 8
                        detail: err.message
88 8
                    }
89
                });
90
            }
91
92
            if (row === undefined) {
93
                db.run("INSERT INTO apikeys (key, email) VALUES (?, ?)",
94
                    apiKey,
95
                    email, (err) => {
96
                        if (err) {
97
                            return res.status(401).json({
98
                                errors: {
99 8
                                    status: 401,
100 8
                                    source: path,
101
                                    title: "Database error",
102
                                    detail: err.message
103 8
                                }
104
                            });
105
                        }
106
107
                        res.json({ data: { key: apiKey }});
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
108
                    });
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
109
            } else {
110
                getUniqueAPIKey(res, email);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
111
            }
112
        });
113
    }
114 8
115
    function login(res, body) {
116
        const email = body.email;
117
        const password = body.password;
118
        const apiKey = body.api_key;
119
120
        if (!email || !password) {
121
            return res.status(401).json({
122
                errors: {
123 6
                    status: 401,
124 6
                    source: "/login",
125 6
                    title: "Email or password missing",
126
                    detail: "Email or password missing in request"
127 6
                }
128 2
            });
129
        }
130
131
        db.get("SELECT * FROM users WHERE apiKey = ? AND email = ?",
132
            apiKey,
133
            email,
134 View Code Duplication
            (err, rows) => {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
135
                if (err) {
136
                    return res.status(500).json({
137
                        errors: {
138 4
                            status: 500,
139
                            source: "/login",
140
                            title: "Database error",
141
                            detail: err.message
142 4
                        }
143
                    });
144
                }
145
146
                if (rows === undefined) {
147
                    return res.status(401).json({
148
                        errors: {
149
                            status: 401,
150
                            source: "/login",
151
                            title: "User not found",
152
                            detail: "User with provided email not found."
153 4
                        }
154 1
                    });
155
                }
156
157
                const user = rows;
158
159
                bcrypt.compare(password, user.password, (err, result) => {
160
                    if (err) {
161
                        return res.status(500).json({
162
                            errors: {
163
                                status: 500,
164 3
                                source: "/login",
165
                                title: "bcrypt error",
166 3
                                detail: "bcrypt error"
167 3
                            }
168
                        });
169
                    }
170
171
                    if (result) {
172
                        let payload = { api_key: user.apiKey, email: user.email };
173
                        let jwtToken = jwt.sign(payload, jwtSecret, { expiresIn: '24h' });
174
175
                        return res.json({
176
                            data: {
177
                                type: "success",
178 3
                                message: "User logged in",
179 2
                                user: payload,
180 2
                                token: jwtToken
181
                            }
182 2
                        });
183
                    } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
184
                        return res.status(401).json({
185
                            errors: {
186
                                status: 401,
187
                                source: "/login",
188
                                title: "Wrong password",
189
                                detail: "Password is incorrect."
190
                            }
191 1
                        });
192
                    }
193
                });
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
194
            });
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
195
    }
196
197
    function register(res, body) {
198
        const email = body.email;
199
        const password = body.password;
200
        const apiKey = body.api_key;
201
202
        if (!email || !password) {
203
            return res.status(401).json({
204
                errors: {
205 5
                    status: 401,
206 5
                    source: "/register",
207 5
                    title: "Email or password missing",
208
                    detail: "Email or password missing in request"
209 5
                }
210 2
            });
211
        }
212
213 View Code Duplication
        bcrypt.hash(password, 10, function(err, hash) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
214
            if (err) {
215
                return res.status(500).json({
216
                    errors: {
217
                        status: 500,
218
                        source: "/register",
219
                        title: "bcrypt error",
220 3
                        detail: "bcrypt error"
221 3
                    }
222
                });
223
            }
224
225
            db.run("INSERT INTO users (apiKey, email, password) VALUES (?, ?, ?)",
226
                apiKey,
227
                email,
228
                hash, (err) => {
229
                    if (err) {
230
                        return res.status(500).json({
231
                            errors: {
232 3
                                status: 500,
233
                                source: "/register",
234
                                title: "Database error",
235
                                detail: err.message
236 3
                            }
237 1
                        });
238
                    }
239
240
                    res.status(201).json({
241
                        data: {
242
                            message: "User successfully registered."
243
                        }
244
                    });
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
245
                });
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
246
        });
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
247 2
    }
248
249
    function checkToken(req, res, next) {
250
        var token = req.headers['x-access-token'];
251
252 View Code Duplication
        if (token) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
253
            jwt.verify(token, jwtSecret, function(err, decoded) {
254
                if (err) {
255
                    return res.status(500).json({
256
                        errors: {
257 13
                            status: 500,
258
                            source: req.path,
259 13
                            title: "Failed authentication",
260 10
                            detail: err.message
261 10
                        }
262
                    });
263
                }
264
265
                req.user = {};
266
                req.user.api_key = decoded.api_key;
267
                req.user.email = decoded.email;
268
269
                next();
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
270
            });
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
271
        } else {
272 10
            return res.status(401).json({
273 10
                errors: {
274 10
                    status: 401,
275
                    source: req.path,
276 10
                    title: "No token",
277
                    detail: "No token provided in request headers"
278
                }
279 3
            });
280
        }
281
    }
282
283
    return {
284
        isValidAPIKey: isValidAPIKey,
285
        getNewAPIKey: getNewAPIKey,
286
        login: login,
287
        register: register,
288
        checkToken: checkToken
289
    };
290
}());
291